using System;
using UnityEngine;
using System.Collections;

public class BoidBehaviour : SteeringBehaviour
{
	
	private ArrayList agents;
	private ArrayList herders;
	
	private Vector3 position;
	private Vector3 destination;
	private Vector3 new_direction;
	private bool	new_direction_set = false;	
	
	private ArrayList neighbours;
	private ArrayList direction_vectors;
	
	private float weight_separation = 3.0f; 	// percentage
	private float weight_alignment = 2.0f; 		// percentage
	private float weight_cohesion = 2.0f; 		// percentage
	
	private const float max_distance_nearest_neighbour = 300.0f;
	private const float max_distance_separation = 50.0f;
	private const float max_distance_cohesion = 150.0f;
	private const int max_nearest_neighbour = 10;
	
	
	private const int angle_see_left = 320;
	private const int angle_see_right = 40;
	
	
	public void Start ()
	{
		//Get a list of agents from the game controller so that it can remove and add agents at will
		this.agents = (((GameController)GameObject.
			FindWithTag("GameController").GetComponent("GameController")).getAgents());		
	}
	
	public void set_seperation_weight(float weight)
	{
		weight_separation = weight;
	}
	
	public void set_alignment_weight(float weight)
	{
		weight_alignment = weight;
	}
	
	public void set_cohesion_weight(float weight)
	{
		weight_cohesion = weight;
	}

	public void setWeights(float sep, float align, float coh)
	{
		weight_separation = sep;	
		weight_alignment = align;	
		weight_cohesion = coh;	
	}
	
	public float get_seperation_weight()
	{
		return weight_separation;
	}
	
	public float get_alignment_weight()
	{
		return weight_alignment;
	}
	
	public float get_cohesion_weight()
	{
		return weight_cohesion;
	}
	
	public override Vector3 getSteering (AgentBehaviourScript agent)
	{
		this.position = agent.getPosition();
		this.destination = agent.getDestination();
		this.direction_vectors = new ArrayList();
		
		this.nearest_neighbours();
		
		Vector3 align = alignment(agent) - position;
		Vector3 coh = cohesion(agent) - position;
		Vector3 sep = separation(agent) - position;
		
		//Debug.DrawLine(position, coh + position, Color.red);
		//Debug.DrawLine(position, align + position, Color.cyan);
		//Debug.DrawLine(position, sep + position, Color.green);
		
		Vector3 dir = align * weight_alignment
						+ coh * weight_cohesion
						+ sep * weight_separation;
		
		return position + 3.0f * (dir / (weight_cohesion + weight_separation + weight_alignment));
	}
	
	// calculates a vector to move away from the herd
	private Vector3 separation(AgentBehaviourScript agent)
	{
		if(this.neighbours.Count == 0)
			return agent.getPosition();
		
		ArrayList vectors = new ArrayList();
		
		foreach(AgentBehaviourScript neighbour in this.neighbours)
		{
			vectors.Add(neighbour.getPosition());
		}
		
		Vector3 centerOfGroup = this.averageOfVectors(vectors);
		Vector3 vecFromCenter = agent.getPosition() - centerOfGroup;
	 	vecFromCenter.Normalize();
		Vector3 final = vecFromCenter * 25 + agent.getPosition(); 
		return final;
	}
    
	// calculates a vector to move with the herd
    private Vector3 alignment(AgentBehaviourScript agent)
    {
            //print("alignment");
            if (neighbours.Count == 0)
                    return agent.getPosition();

            ArrayList vectors = new ArrayList();

            foreach(AgentBehaviourScript neighbour in this.neighbours)
            {
                    float angle1 = Vector3.Angle(this.position, neighbour.getPosition());

                    // if the agent is nearby, calculcate a vector to align
                    if(angle1 < angle_see_right || angle1 > angle_see_left)
                    {   

                    vectors.Add(neighbour.rigidbody.velocity.normalized);
                    }
            }

            return agent.getPosition() + averageOfVectors(vectors)*50;
    }
	
	// calculates a vector to move towards the herd
	private Vector3 cohesion(AgentBehaviourScript agent)
	{
		if(this.neighbours.Count == 0)
			return agent.getPosition();
		
		ArrayList vectors = new ArrayList();		
		foreach(AgentBehaviourScript neighbour in this.neighbours)
		{
			vectors.Add(neighbour.getPosition());
		}

		return averageOfVectors(vectors);
	}

	// sets an arraylist of cloest agents that are within a max distance 
	private void nearest_neighbours()
	{
		//print("nearest_neighbours");
		this.neighbours = new ArrayList();
		
		bool neighbours_enabled = (((GameController)GameObject.
			FindWithTag("GameController").GetComponent("GameController")).neighbours_enabled);
		
        foreach (GameObject agent in agents) 
		{
			Vector3 agent_position = ((AgentBehaviourScript)agent.GetComponent("AgentBehaviourScript")).getPosition();
			float dist = Vector3.Distance(this.position, agent_position);
			
			if(dist > 0 && dist < max_distance_nearest_neighbour && this.neighbours.Count < max_nearest_neighbour)
			{			
				// This looks cool, might have to put a gui button in for it.
				if(neighbours_enabled)
				{
					Debug.DrawLine(position, agent_position, Color.magenta);
				}
				
				this.neighbours.Add(((AgentBehaviourScript)agent.GetComponent("AgentBehaviourScript")));
			}
        }
	}
	
	// Returns a average vector from an array of vectors
	// @param  ArrayList vectors - vectors to blend
	// @return Vector3 - blended vector
	//
	private Vector3 averageOfVectors(ArrayList vectors)
	{
		if(vectors.Count == 0)
			return new Vector3(0,0,0);
		
		Vector3 result = new Vector3();
		
		foreach (Vector3 vector in vectors)	
			result += vector;
		return result/vectors.Count;
				
	}	

}
